#include "stdafx.h"
#include "libraries.h"

#include "Application.h"
#include "Filesystem.h"
#include "DefaultGUIStyle.h"
#include "Random.h"

using namespace Cvekas;

const uint screen_width = 800;
const uint screen_height = 600;
float shrink_ratio = 0.9f;
float hex_radius = 32.0f;
float gap = 0.7f;

const Color red(196, 64, 64);
const Color blue(64, 64, 196);

const Vector2 board_pos(64.0f, 64.0f);

class Hexaflip: public Application
{
public:
	Hexaflip(HINSTANCE instance, const std::string& name, const std::string& log_filename)
		: Application(instance, name, log_filename)
	{
		Filesystem::init();
	}

	virtual ~Hexaflip()
	{
	}

	virtual void initVideoDevice()
	{
		settings->insert("video", VideoSettings());

		(*settings)["video"]["ScreenWidth"] = 1024;
		(*settings)["video"]["ScreenHeight"] = 768;
		(*settings)["video"]["VSync"] = true;
		(*settings)["video"]["Fullscreen"] = false;

		window = WindowPtr(new Window(instance, name));
		window->showCursor(false);
		video_device->init(window);
	}

	int row_width(int row)
	{
		int width;
		if(row < size)
			width = row+1;
		else if(row > board_height - size)
			width = board_height - row;
		else
			width = (row - size) % 2 ? size : size - 1;
		return width;
	}

	void draw_hex(const Vector2& center, float r, const Color& color)
	{
		hex_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f), RectF(center - Vector2(r, r), Vector2(2.0f*r, 2.0f*r)), color); 
	}

	void draw_board(const Vector2& pos, int size)
	{
		for(int y = 0; y < board_height; ++y)
		{
			int width = row_width(y);

			float x_shift;
			if(size%2)
				x_shift = float((size - width) / 2) * (hex_radius + gap) * 3.0f;
			else
				x_shift = float((size + 1 - width) / 2) * (hex_radius + gap) * 3.0f - (hex_radius + gap) * 1.5f;

			float x_start = pos.x + (y%2 ? (hex_radius + gap) * 1.5f: 0.0f) + x_shift;
			float y_coord = pos.y + float(y) * (hex_radius + gap) * sqrt(3.0f) / 2.0f;
			for(int x = 0; x < width; x++)
			{
				uint state = cell_state[y * board_width + x];
				float size = cell_size[y * board_width + x];
				float radius = hex_radius * size;

				if(state & FLIPPING)
				{
					Color col = Color::lerp(blue, red, state&1 ? 1.0f - size : size); 
					draw_hex(Vector2(x_start + float(x*2) * (hex_radius + gap) * 1.5f, y_coord), hex_radius * shrink_ratio, col);
					continue;
				}

				if(state & 1)
					draw_hex(Vector2(x_start + float(x*2) * (hex_radius + gap) * 1.5f, y_coord), radius, blue);
				else
					draw_hex(Vector2(x_start + float(x*2) * (hex_radius + gap) * 1.5f, y_coord), radius, red);
			}
		}
	}

	bool collide_point_to_hex(const Vector2& p, const Vector2& c, float r)
	{
		const Vector2 pt(p - c);
		if(pt.lengthSq() > r*r)
			return false;

		const Vector3 line1(cos(radians(30.0f)), sin(radians(30.0f)), sqrt(3.0f) / 2.0f * r);
		const Vector3 line2(cos(radians(330.0f)), sin(radians(330.0f)), sqrt(3.0f) / 2.0f * r);
		const Vector3 line3(cos(radians(150.0f)), sin(radians(150.0f)), sqrt(3.0f) / 2.0f * r);
		const Vector3 line4(cos(radians(210.0f)), sin(radians(210.0f)), sqrt(3.0f) / 2.0f * r);

		float d1 = (line1.x * pt.x) + (line1.y * pt.y) + line1.z;
		float d2 = (line2.x * pt.x) + (line2.y * pt.y) + line2.z;
		float d3 = (line3.x * pt.x) + (line3.y * pt.y) + line3.z;
		float d4 = (line4.x * pt.x) + (line4.y * pt.y) + line4.z;

		float mind = min(min(d1, d2), min(d3, d4));

		return mind > 0.0f;
	}

	void handle_mouse(const Vector2& pos, int size)
	{
		MousePos mouse = input->getMouse()->getAbsolutePos();
		Vector2 mousef(float(mouse.x), float(mouse.y));

		for(int y = 0; y < board_height; ++y)
		{
			float y_coord = pos.y + float(y) * (hex_radius + gap) * sqrt(3.0f) / 2.0f;

			if(abs(mousef.y - y_coord) > sqrtf(3.0f) / 2.0f * hex_radius)
				continue;

			int width = row_width(y);

			float x_shift;
			if(size%2)
				x_shift = float((size - width) / 2) * (hex_radius + gap) * 3.0f;
			else
				x_shift = float((size + 1 - width) / 2) * (hex_radius + gap) * 3.0f - (hex_radius + gap) * 1.5f;

			float x_start = pos.x + (y%2 ? (hex_radius + gap) * 1.5f : 0.0f) + x_shift;			
			for(int x = 0; x < width; x++)
			{
				Vector2 center(x_start + float(x*2) * (hex_radius + gap) * 1.5f, y_coord);

				if(abs(mousef.x - center.x) > hex_radius)
					continue;

				if(collide_point_to_hex(mousef, center, hex_radius))
				{
					mark_cross(direction, input->getMouse()->isButtonDown(Mouse::BTN_LEFT), x, y);

					cell_state[y * board_width + x] |= HOT;
					if(input->getMouse()->isButtonDown(Mouse::BTN_LEFT))
					{
						cell_state[y * board_width + x] |= FLIPPING;
						direction = !direction;
					}
					return;
				}
			}
		}
	}

	void scramble(uint steps)
	{
		Random r;
		
		direction = false;
		while(steps--)
		{
			int y = r.randInt(0, board_height);
			int x = r.randInt(0, row_width(y));

			mark_cross(direction, true, x, y);
			cell_state[y * board_width + x] |= FLIPPING;

			direction = !direction;

			for(uint i = 0; i < cell_state.size(); ++i)
			{
				uint& state = cell_state[i];

				if(state & FLIPPING)
				{
					state &= 1;
					state ^= 1;
				}
			}
		}
		direction = false;
	}

	void mark_cross(bool type, bool flip, int x, int y)
	{
		int width = row_width(y);
		x = x*2 + size - width;

		uint flip_col = flip ? 1 : 0;
		uint set_flip = flip ? FLIPPING : 0;

		int ny = y;
		if(type)
		{
			while((ny+=2) < board_height)
			{
				width = row_width(ny);
				int nx = (x - (size - width))/2;
				if(nx >= 0 && nx < width)
				{
					cell_state[ny * board_width + nx] |= HOT | set_flip;
				}
			}

			ny = y;
			while(--ny >= 0)
			{
				width = row_width(ny);
				int nx1 = ((x + y - ny) - (size - width))/2;
				int nx2 = ((x - y + ny) - (size - width))/2;
				if(nx1 >= 0 && nx1 < width)
				{
					cell_state[ny * board_width + nx1] |= HOT | set_flip;
				}
				if(nx2 >= 0 && nx2 < width)
				{
					cell_state[ny * board_width + nx2] |= HOT | set_flip;
				}
			}
		}
		else
		{
			while((ny-=2) >= 0)
			{
				width = row_width(ny);
				int nx = (x - (size - width))/2;
				if(nx >= 0 && nx < width)
				{
					cell_state[ny * board_width + nx] |= HOT | set_flip;
				}
			}

			ny = y;
			while(++ny < board_height)
			{
				width = row_width(ny);
				int nx1 = ((x + y - ny) - (size - width))/2;
				int nx2 = ((x - y + ny) - (size - width))/2;
				if(nx1 >= 0 && nx1 < width)
				{
					cell_state[ny * board_width + nx1] |= HOT | set_flip;
				}
				if(nx2 >= 0 && nx2 < width)
				{
					cell_state[ny * board_width + nx2] |= HOT | set_flip;
				}
			}
		}
	}

	void update_sizes()
	{
		for(int y = 0; y < board_height; ++y)
		{
			int width = row_width(y);
			for(int x = 0; x < width; ++x)
			{
				float& size = cell_size[y * board_width + x];
				uint& state = cell_state[y * board_width + x];

				if(state & FLIPPING)
				{
					size -= 10.0f * Timer::getTimeScale();
					if(size < 0.0f)
					{
						size = shrink_ratio;
						state |= HOT;
						state &= ~FLIPPING;
						state ^= 1;
					}

					continue;
				}
				
				if(state & HOT)
				{
					size -= 2.0f * Timer::getTimeScale();
					size = max(size, shrink_ratio);
				}
				else
				{
					size += 1.0f * Timer::getTimeScale();
					size = min(size, 1.0f);
				}
			}
		}

	}

	virtual void init()
	{
		input->getMouse()->setVirtualResolution(screen_width, screen_height);

		sprite_batch = video_device->createSpriteBatch(main_renderer, screen_width, screen_height, 1);
		DefaultGUIStylePtr gui_style = DefaultGUIStylePtr(new DefaultGUIStyle(sprite_batch));
		gui = GUIPtr(new GUI(gui_style, input));

		EffectPtr hex_effect = effect_manager->load("HexBatch", Filesystem::redirect("HexBatch.fx"));
		effect_manager->remove(hex_effect->getId());
		hex_effect = effect_manager->load("HexBatch", Filesystem::redirect("HexBatch.fx"));
		hex_batch = video_device->createSpriteBatch(main_renderer, screen_width, screen_height, 0);
		hex_batch->setEffect(hex_effect);

		size = 5;
		board_width = size;
		board_height = (size*2 - 1) + (size*2 - 2);
		cell_state.assign(board_width * board_height, RED);
		cell_size.assign(board_width * board_height, 1.0f);

		scramble(3);
	}

	virtual bool update()
	{
		input->getMouse()->update();

		for(uint i = 0; i < cell_state.size(); ++i)
			cell_state[i] &= ~HOT;

		handle_mouse(board_pos, size);
		update_sizes();
		return true;
	}

	virtual bool render()
	{
		video_device->setClearColor(Color(255, 255, 255));
		video_device->beginScene();

		draw_board(board_pos, size);

		//gap = (gui->doSlider(Vector2(500.0f, 10.0f)) - 0.5f) * 64.0f;
		//hex_radius = (gui->doSlider(Vector2(500.0f, 60.0f))) * 64.0f;

		gui->present();
		sprite_batch->present();
		hex_batch->present();
		main_renderer->render();
		video_device->endScene();
		main_renderer->endFrame();

		return true;
	}

private:
	SpriteBatchPtr hex_batch;
	SpriteBatchPtr sprite_batch;
	GUIPtr gui;

	enum CellState
	{
		RED = 0,
		BLUE = 1,
		HOT = 2,
		FLIPPING = 4
	};

	int size, board_width, board_height;
	std::vector<uint> cell_state;
	std::vector<float> cell_size;

	bool direction;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd)
{
	try
	{
		Hexaflip app(hInstance, "Hexaflip", "hexaflip.log");
		app.run();
	}
	catch (std::exception& e)
	{
		MessageBox(NULL, e.what(), "Fatal error", NULL);
	}
	return 0;
}
